/*
 * XCMouse
 * http://code.google.com/p/bit-eclipse/wiki/XCMouse
 *
 * Copyright 2012, Denzel Morris
 * Licensed under the MIT License.
 * See License.txt
 */

#include <cmath>
#include <boost/chrono.hpp>
#include "ControllerThread.h"
#include "ControllerUtility.h"

ControllerThread::ControllerThread(boost::function1<void, const ControllerState&> slot) {
	mSignal.connect(slot);

	ZeroMemory(&mLastState, sizeof(XINPUT_STATE));
	ZeroMemory(&mCurrentState, sizeof(XINPUT_STATE));
}

void ControllerThread::start() {
	mThread = boost::thread(&ControllerThread::_process, this);
}

void ControllerThread::stop() {
	mThread.detach();
}

void ControllerThread::_process() {
	using namespace boost::chrono;
	while( 1 ) {
		float ms = 0.f;
		steady_clock::time_point start = steady_clock::now();
		do {
			steady_clock::duration interval = steady_clock::now() - start;
			ms = interval.count() * 1e-6; // 1 ns = 1e-6 ms

			//
			// Sleep a little so other applications can play, albeit
			// we will probably go longer than our intended update
			// frequency, but that's not critical
			//
			boost::this_thread::sleep(
				boost::posix_time::milliseconds(16.67f - ms)
			);
		} while( ms < 16.67f);

		using namespace ControllerUtility;
		mLastState = mCurrentState;
		if( !IsAControllerConnected(&mCurrentState) ) {
			//
			// For now no one really cares whether the controller
			// is connected or not, fail silently
			//
			continue;
		}

		ControllerState s;
		NormalizeAnalogSticks(
			mCurrentState,
			&s.ldx, &s.ldy,
			&s.rdx, &s.rdy
		);

		//
		// Invert the y-axis to account for difference from
		// controller to screen
		//
		s.ldy = -s.ldy;
		s.rdy = -s.rdy;

		//
		// Account for deadzone
		//
		const float lmagnitude = std::sqrt(s.ldx * s.ldx + s.ldy * s.ldy);
		const float rmagnitude = std::sqrt(s.rdx * s.rdx + s.rdy * s.rdy);
		if( lmagnitude < 0.2f ) {
			s.ldy = s.ldx = 0.f;
		}
		if( rmagnitude < 0.2f ) {
			s.rdy = s.rdx = 0.f;
		}

		//
		// For me, this makes it easier to extend later when
		// I want to map new buttons. Simply add them to the
		// map, and modify the bounds of the loop
		//
		static const struct {
			DWORD xflag;
			unsigned int uflag, dflag;
		} fmap[4] = {
			{ XINPUT_GAMEPAD_A, BUTTON_A_UP, BUTTON_A_DOWN },
			{ XINPUT_GAMEPAD_B, BUTTON_B_UP, BUTTON_B_DOWN },
			{ XINPUT_GAMEPAD_X, BUTTON_X_UP, BUTTON_X_DOWN },
			{ XINPUT_GAMEPAD_Y, BUTTON_Y_UP, BUTTON_Y_DOWN }
		};

		//
		// If the state isn't the same as the last one, then
		// we must check any buttons for changes
		//
		if( !IsStateSame(mCurrentState, mLastState) ) {
			const DWORD cButtons = mCurrentState.Gamepad.wButtons;
			const DWORD lButtons = mLastState.Gamepad.wButtons;
			//
			// Perform a little bit-masking. Once again, for me
			// this is clearer than a slew of if-then statements
			//
			for( int i = 0; i < 4; ++ i ) {
				const long cb = (long)cButtons & fmap[i].xflag;
				const long lb = (long)lButtons & fmap[i].xflag;
				if( (cb - lb) == 0 ) continue; // no change
				const unsigned long diff = (cb - lb) > 0; // 1 if down, 0 if up
				const unsigned long mask = diff - 1; // 0x0 if down, 0xF if up
				s.ButtonsFlag |= (fmap[i].uflag & mask) | (fmap[i].dflag & ~mask);
			}
		}

		//
		// After all that hardwork, signal the event
		//
		mSignal(s);
	}
}